#include <stdio.h>
#include <stdlib.h>
//#include <memory.h>

#include <deque>
using namespace std;

#include <windows.h>

#define UINT8 unsigned char
#define UINT16 unsigned short
#define UINT32 unsigned int
#define INT8 signed char
#define INT16 signed short
#define INT32 signed int
#define SINT32 signed int

#include "i286x/cpucore.h"


bool *mapped;

extern bool hook_trace;
extern int trace_map;

extern FILE* fp_hook;
extern FILE* fp_trace;
extern FILE* fp_call;

#define STATES 3
extern unsigned int *rd_mode, *wr_mode, *ppu_mode, *pc_mode;
extern unsigned int *rd_low, *rd_high;
extern unsigned int *wr_low, *wr_high;
extern unsigned int *ppu_low, *ppu_high;
extern unsigned int *pc_low, *pc_high;
extern unsigned int *pc_start;

extern int trace_map;
extern void Dis_8086(char *buffer, UINT32 address);


typedef deque<int> BYTE_LIST;
typedef struct
{
	int trap_addr;
	BYTE_LIST check_list;
	BYTE_LIST mod_list;
} PATCHES;
typedef deque<PATCHES>::iterator PATCH_ITER;

deque<PATCHES> patch_list;


extern "C"
{
	int hook_pc;
	int hook_address;
	int hook_value;

	void hook_read_byte();
	void hook_read_word();
	void hook_write_byte();
	void hook_write_word();

	void Hook_Dummy();
}


void Hook_Dummy()
{
	return;
}


void DeInitDebug()
{
	if( fp_hook )
	{
		fclose( fp_hook );
	}

	if( rd_mode )
	{
		delete[] rd_mode;
		delete[] wr_mode;
		delete[] pc_mode;
		delete[] ppu_mode;

		delete[] rd_low;
		delete[] wr_low;
		delete[] pc_low;
		delete[] ppu_low;

		delete[] rd_high;
		delete[] wr_high;
		delete[] pc_high;
		delete[] ppu_high;

		delete[] pc_start;
	}

	if( fp_trace )
	{
		delete[] mapped;
		fclose( fp_trace );
	}
}


void Load_Patches()
{
	FILE *dump_log = fopen( "patches.txt", "r" );
	if( dump_log )
	{
		PATCHES patch_type;

		// parse list
		while(1)
		{
			char line[512];

			// read line
			line[0] = 0;
			fgets( line, 512, dump_log );

			if( line[0]==0 && feof(dump_log) ) break;
			if(line[0]==';') continue;
			if(line[0]==0x0d) continue;
			if(line[0]==0x0a) continue;
			if(line[0]==' ') continue;
			if(line[0]=='/' && line[1]=='/') continue;

			// -------------------------------------------------------------------
			// -------------------------------------------------------------------
			// -------------------------------------------------------------------

			char mode[64];
			sscanf( line, "%s", mode );


			// Start address
			if( strcmp(mode,"TRAP") == 0 )
			{
				patch_type.trap_addr = 0;
				patch_type.check_list.clear();
				patch_type.mod_list.clear();

				sscanf( line, "TRAP %X", &patch_type.trap_addr );
				continue;
			}

			// Assert conditions
			if( strcmp(mode,"CHECK") == 0 )
			{
				int ptr;
				int arg[16];
				int count;
				int pos;

				count = sscanf( line, "CHECK %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X",
					&ptr,
					&arg[0x0],&arg[0x1],&arg[0x2],&arg[0x3],
					&arg[0x4],&arg[0x5],&arg[0x6],&arg[0x7],
					&arg[0x8],&arg[0x9],&arg[0xa],&arg[0xb],
					&arg[0xc],&arg[0xd],&arg[0xe],&arg[0xf]
				);
				pos = patch_type.check_list.size();
				count--;

				patch_type.check_list.push_back( ptr );
				for( int lcv=0; lcv<count; lcv++ )
				{
					patch_type.check_list.push_back( arg[lcv] );
				}
				continue;
			}

			// Replace bytes
			if( strcmp(mode,"PATCH") == 0 )
			{
				int ptr;
				int arg[16];
				int count;
				int pos;

				count = sscanf( line, "PATCH %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X",
					&ptr,
					&arg[0x0],&arg[0x1],&arg[0x2],&arg[0x3],
					&arg[0x4],&arg[0x5],&arg[0x6],&arg[0x7],
					&arg[0x8],&arg[0x9],&arg[0xa],&arg[0xb],
					&arg[0xc],&arg[0xd],&arg[0xe],&arg[0xf]
				);
				pos = patch_type.mod_list.size();
				count--;

				patch_type.mod_list.push_back( ptr );
				for( int lcv=0; lcv<count; lcv++ )
				{
					patch_type.mod_list.push_back( arg[lcv] );
				}
				continue;
			}

			// ADD patch code
			if( strcmp(mode,"ADD") == 0 )
			{
				goto ADD_LIST_PTR;
			}


			// fail-safe
			continue;

			// -------------------------------------------------------------------
			// -------------------------------------------------------------------
			// -------------------------------------------------------------------

		ADD_LIST_PTR:
			PATCH_ITER iter = patch_list.begin();

			// OPTIMIZE - put in order
			while( iter != patch_list.end() )
			{
				if( (patch_type.trap_addr) < (*iter).trap_addr ) break;
				iter++;
			}

			// add to SORTED list
			patch_list.insert( iter, patch_type );
		}

		if(dump_log) fclose(dump_log);
	}
}


char buffer[1024];
void Print_Instruction( FILE *trace )
{
	memset( buffer, ' ', 1024 );
	fprintf( trace, "[%01x:%04x][%04x:%04x] ",
		(CS_BASE+CPU_IP)>>16, (CS_BASE+CPU_IP)&0xffff,
		CPU_CS, CPU_IP );

	Dis_8086( buffer, 0 );
	fprintf( trace, "%-43s", buffer );

	fprintf( trace, " ax=%04x bx=%04x cx=%04x dx=%04x || bp=%04x sp=%04x si=%04x di=%04x || cs=%04x ds=%04x ss=%04x es=%04x",
		CPU_AX, CPU_BX, CPU_CX, CPU_DX, 
		CPU_BP, CPU_SP, CPU_SI, CPU_DI,
		CPU_CS, CPU_DS, CPU_SS, CPU_ES
	);

	fprintf( trace, " || %c%c%c%c",
		CPU_FLAG&0x0800 ? 'V' : 'v',
		CPU_FLAG&0x0080 ? 'N' : 'n',
		CPU_FLAG&0x0040 ? 'Z' : 'z',
		CPU_FLAG&0x0001 ? 'C' : 'c' );

	fprintf( trace, "\n" );
}


void Trace_8086()
{
	int bank,offset;
	int type;

	hook_pc = CPU_IP + CS_BASE;


	//if(hook_pc==0x1559d)
		//hook_pc+=0;


	// soft-patching
	for( int lcv1=0; lcv1<patch_list.size(); lcv1++ )
	{
		// not a match
		if( hook_pc != patch_list[lcv1].trap_addr ) continue;

		// =================================================================
		// =================================================================

		// assert bytes match
		int base = patch_list[lcv1].check_list[0];
		bool okay = true;
		for( int lcv2=1; lcv2<patch_list[lcv1].check_list.size(); lcv2++ )
		{
			if( mem[base+lcv2-1] != patch_list[lcv1].check_list[lcv2] )
			{ okay = false; break; }
		}
		if( !okay ) continue;

		// =================================================================
		// =================================================================

		// replace bytes
		base = patch_list[lcv1].mod_list[0];
		for( lcv2=1; lcv2<patch_list[lcv1].mod_list.size(); lcv2++ )
		{
			mem[base+lcv2-1] = patch_list[lcv1].mod_list[lcv2];
		}
	}


	// Trace.txt
	if( trace_map )
	{
		if( !fp_trace )
		{
			fp_trace = fopen( "trace.log", "w" );
			mapped = new bool[ 0x20*0x10000 ];
			memset( mapped,0,0x20*0x10000 );
		}

		if( !mapped[ CPU_IP+CS_BASE ] )
		{
			Print_Instruction( fp_trace );
			mapped[ (CPU_IP+CS_BASE) ] = true;
		}
	}


	// Hook.txt
	if( hook_trace )
	{
		for( int lcv = 0; lcv < STATES; lcv++ )
		{
			FILE *out;

			// start-stop
			if( pc_mode[ lcv ] & 1 )
			{
				if( hook_pc == pc_low[lcv] )
					pc_start[lcv] = 1;

				if( !pc_start[lcv] ) continue;

				out = fp_hook;
			}
			// low-high
			else
			{
				// fail: outside boundaries
				if( hook_pc < pc_low[ lcv ] ) continue;
				if( hook_pc > pc_high[ lcv ] ) continue;
			}

// ------------------------------------------------------

			if( pc_mode[ lcv ] >= 4 && pc_mode[ lcv ] < 8 )
			{
				pc_mode[ lcv ] &= 3;
				trace_map = 1;
			}

			// output file mode
			out = ( pc_mode[ lcv ] <= 1 ) ? fp_hook : fp_trace;

			if( !out ) continue;
			if( out == fp_trace )
				fprintf( out, "** " );

			
			int old_pc = hook_pc;
			Print_Instruction( out );
			hook_pc = old_pc;


			// end formatting
			if( hook_pc == pc_high[lcv] )
			{
				pc_start[lcv] = 0;

				if( pc_low[ lcv ] != pc_high[ lcv ] )
					fprintf( out, "\n" );
			}
		} // end STATES
	} // end hook
}


void hook_read_byte()
{
	unsigned int start, stop;

	if( !hook_trace ) return;

	hook_pc = CPU_IP + CS_BASE;
	
	hook_pc &= 0x00ffffff;
	hook_address &= 0x00ffffff;

	start = hook_address;
	stop = start + 0;

	for( int lcv = 0; lcv < STATES; lcv++ )
	{
		FILE *out;

		// linear map
		if( rd_mode[ lcv ] & 1 )
		{
			// fail: outside boundaries
			if( stop < rd_low[ lcv ] ) continue;
			if( start > rd_high[ lcv ] ) continue;
		}
		// shadow map
		else
		{
			// fail: outside boundaries
			if( ( stop & 0xffff ) < ( rd_low[ lcv ] & 0xffff ) ) continue;
			if( ( start & 0xffff ) > ( rd_high[ lcv ] & 0xffff ) ) continue;
			
			if( ( stop >> 16 ) < ( rd_low[ lcv ] >> 16 ) ) continue;
			if( ( start >> 16 ) > ( rd_high[ lcv ] >> 16 ) ) continue;
		}

// ------------------------------------------------------

		// auto-trace
		if( rd_mode[ lcv ] >= 4 && rd_mode[ lcv ] < 8 )
		{
			rd_mode[ lcv ] &= 3;
			trace_map = 1;

			hook_trace = 0;
			Trace_8086();
			hook_trace = 1;
		}

		// output file mode
		out = ( rd_mode[ lcv ] <= 1 ) ? fp_hook : fp_trace;

		if( !out ) continue;
		if( out == fp_trace )
			fprintf( out, "** " );

		fprintf( out, "[%01X:%04X] %s = %02X [%06X]\n",
			hook_pc >> 16, hook_pc & 0xffff, "R08",
			hook_value & 0xff, hook_address );
	}
}


void hook_read_word()
{
	unsigned int start, stop;

	if( !hook_trace ) return;

	hook_pc = CPU_IP + CS_BASE;

	hook_pc &= 0x00ffffff;
	hook_address &= 0x00ffffff;

	start = hook_address;
	stop = start + 1;

	for( int lcv = 0; lcv < STATES; lcv++ )
	{
		FILE *out;

		// linear map
		if( rd_mode[ lcv ] & 1 )
		{
			// fail: outside boundaries
			if( stop < rd_low[ lcv ] ) continue;
			if( start > rd_high[ lcv ] ) continue;
		}
		// shadow map
		else
		{
			// fail: outside boundaries
			if( ( stop & 0xffff ) < ( rd_low[ lcv ] & 0xffff ) ) continue;
			if( ( start & 0xffff ) > ( rd_high[ lcv ] & 0xffff ) ) continue;
			
			if( ( stop >> 16 ) < ( rd_low[ lcv ] >> 16 ) ) continue;
			if( ( start >> 16 ) > ( rd_high[ lcv ] >> 16 ) ) continue;
		}

// ------------------------------------------------------

		// auto-trace
		if( rd_mode[ lcv ] >= 4 && rd_mode[ lcv ] < 8 )
		{
			rd_mode[ lcv ] &= 3;
			trace_map = 1;

			hook_trace = 0;
			Trace_8086();
			hook_trace = 1;
		}

		// output file mode
		out = ( rd_mode[ lcv ] <= 1 ) ? fp_hook : fp_trace;

		if( !out ) continue;
		if( out == fp_trace )
			fprintf( out, "** " );

		fprintf( out, "[%01X:%04X] %s = %04X [%06X]\n",
			hook_pc >> 16, hook_pc & 0xffff, "R16",
			hook_value & 0xffff, hook_address );
	}
}


void hook_write_byte()
{
	unsigned int start, stop;

	// reset PC tracer
	hook_address &= 0x00ffffff;
	if( mapped && hook_address < 0x200000 )
		mapped[ hook_address ] = false;

	if( !hook_trace ) return;

	hook_pc = CPU_IP + CS_BASE;

	hook_pc &= 0x00ffffff;
	hook_address &= 0x00ffffff;

	start = hook_address;
	stop = start + 0;

	for( int lcv = 0; lcv < STATES; lcv++ )
	{
		FILE *out;

		// linear map
		if( wr_mode[ lcv ] & 1 )
		{
			// fail: outside boundaries
			if( stop < wr_low[ lcv ] ) continue;
			if( start > wr_high[ lcv ] ) continue;
		}
		// shadow map
		else
		{
			// fail: outside boundaries
			if( ( stop & 0xffff ) < ( wr_low[ lcv ] & 0xffff ) ) continue;
			if( ( start & 0xffff ) > ( wr_high[ lcv ] & 0xffff ) ) continue;
			
			if( ( stop >> 16 ) < ( wr_low[ lcv ] >> 16 ) ) continue;
			if( ( start >> 16 ) > ( wr_high[ lcv ] >> 16 ) ) continue;
		}

// ------------------------------------------------------

		// auto-trace
		if( wr_mode[ lcv ] >= 4 && wr_mode[ lcv ] < 8 )
		{
			wr_mode[ lcv ] &= 3;
			trace_map = 1;

			hook_trace = 0;
			Trace_8086();
			hook_trace = 1;
		}

		// output file mode
		out = ( wr_mode[ lcv ] <= 1 ) ? fp_hook : fp_trace;

		if( !out ) continue;
		if( out == fp_trace )
			fprintf( out, "** " );

		fprintf( out, "[%01X:%04X] %s = %02X [%06X]\n",
			hook_pc >> 16, hook_pc & 0xffff, "W08",
			hook_value & 0xff, hook_address );
	}
}


void hook_write_word()
{
	unsigned int start, stop;

	// reset PC tracer
	hook_address &= 0x00ffffff;
	if( mapped && hook_address < 0x200000 )
		mapped[ hook_address ] = false;

	if( !hook_trace ) return;

	hook_pc = CPU_IP + CS_BASE;

	hook_pc &= 0x00ffffff;
	hook_address &= 0x00ffffff;

	start = hook_address;
	stop = start + 1;

	for( int lcv = 0; lcv < STATES; lcv++ )
	{
		FILE *out;

		// linear map
		if( wr_mode[ lcv ] & 1 )
		{
			// fail: outside boundaries
			if( stop < wr_low[ lcv ] ) continue;
			if( start > wr_high[ lcv ] ) continue;
		}
		// shadow map
		else
		{
			// fail: outside boundaries
			if( ( stop & 0xffff ) < ( wr_low[ lcv ] & 0xffff ) ) continue;
			if( ( start & 0xffff ) > ( wr_high[ lcv ] & 0xffff ) ) continue;
			
			if( ( stop >> 16 ) < ( wr_low[ lcv ] >> 16 ) ) continue;
			if( ( start >> 16 ) > ( wr_high[ lcv ] >> 16 ) ) continue;
		}

// ------------------------------------------------------

		// auto-trace
		if( wr_mode[ lcv ] >= 4 && wr_mode[ lcv ] < 8 )
		{
			wr_mode[ lcv ] &= 3;
			trace_map = 1;

			hook_trace = 0;
			Trace_8086();
			hook_trace = 1;
		}

		// output file mode
		out = ( wr_mode[ lcv ] <= 1 ) ? fp_hook : fp_trace;

		if( !out ) continue;
		if( out == fp_trace )
			fprintf( out, "** " );

		fprintf( out, "[%01X:%04X] %s = %04X [%06X]\n",
			hook_pc >> 16, hook_pc & 0xffff, "W16",
			hook_value & 0xffff, hook_address );
	}
}
